這一篇文章我們將要談談常常聽到的 DataMapper 這個東西,應該是有不少人在一些 ORM 的 libary 中有聽到這東西。
A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.
我個人的理解為 :
這個模式的理念在於,將『 domain 』從『 persistence layer ( 可以想成資料庫存取 ) 』分離出來,也就是說你的 domain model 實體中沒有所謂的資料庫處理這一塊。
然後他大部份的使用時機為 :
複雜的業務,且 DataMapper 都會與 Domain Layer 的 Domain Model 一起用
然後有一些重點要注意一下 :
接下來我覺得直接看範例,會比較能理解做啥。
這個範例業務是這樣的 :
有個用戶註冊,但是需要檢查該員工的公司是否已滿額,並且也要 18 歲才能 ( 不能是童工啊 ),但如果是 VIP 公司則沒限制。
// Domain Layer =======================================
class PersonDomain{
id: string
age: number
name: string
company: string
constructor(id:string, age: number, name: string,company: string){
this.id = id
this.age = age
this.name = name
this.company = company
}
isAdult(): boolean{
return this.age >= 18
}
isVIP(): boolean{
return ['HAHOW','GOOGLE','KKBOX'].includes(this.company)
}
}
interface IPersonMapper{
findById(id: string): PersonDomain
findByCompany(company: string): PersonDomain[]
insert(person: PersonDomain): void
update(person: PersonDomain): void
delete(person: PersonDomain): void
}
// DataSource Layer =======================================
function _mockExecuteSelectSql(sql){
return [
{
id: '1',
name: 'mark',
age: 18,
company: 'HAHOW'
}
]
}
class PersonMapper implements IPersonMapper {
findById(id: string): PersonDomain{
const resultSet: any = _mockExecuteSelectSql(`SELECT * FROM person WHERE id=${id}`)
const result = this.doLoad(resultSet)
return result[0]
}
findByCompany(company: string): PersonDomain[]{
console.log('Connect to db for find')
const resultSet: any = _mockExecuteSelectSql(`SELECT * FROM person WHERE company=${company}`)
const result = this.doLoad(resultSet)
return result
}
insert(person: PersonDomain): void{
}
update(person: PersonDomain): void{
}
delete(person: PersonDomain): void{
}
private doLoad(resultSet: any): PersonDomain[]{
const result = []
for (const data of resultSet) {
result.push(new PersonDomain(data.id, data.age, data.name, data.company))
}
return result
}
}
// Application Layer =======================================
const personMapper = new PersonMapper()
const newPerson: PersonDomain = new PersonDomain(null, 18, 'Mark', 'HAHOW')
if(!newPerson.isVIP()){
const personsInCompany: PersonDomain[] = personMapper.findByCompany('HAHOW')
if(personsInCompany.length >= 10) throw Error('公司人數已達限制')
}
if(!newPerson.isAdult()) throw Error('要成年才能註冊喔')
personMapper.insert(newPerson)
DataMapper 是為了要將『 資料儲存 』與 『 Domain Model 』分開而誕生,那反過來思考,如果合在一起會變成怎麼樣呢 ?
我覺得比較大的問題是,很多的資料庫 query 不是為了該 domain 而產生的,例如在實務上我是有為了處理 N+1 的問題來抓某些資料,或是為了其它 domain 來抓資料,但那個 domain 需要的資料與原本的 domain 不相同,這幾種情況。這樣如果分離,就不用被綁死在 domain。
之前開發時有需要使用到 TypeORM,然後我看了現在就有個疑問 。
TypeORM - Active Record vs Data Mapper
你點進去,然後看這一段範例碼,是不是很像我們上面範例的 application 層那的程式碼,然後我的疑問是 :
Repository 就是指 DataMapper 嗎 ?
這個問題我們下集待續。
const userRepository = connection.getRepository(User);
// example how to save DM entity
const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.isActive = true;
await userRepository.save(user);
// example how to remove DM entity
await userRepository.remove(user);
// example how to load DM entities
const users = await userRepository.find({ skip: 2, take: 5 });
const newUsers = await userRepository.find({ isActive: true });
const timber = await userRepository.findOne({ firstName: "Timber", lastName: "Saw" });
事實上我還不確定要如何運用呢 ~ 因為有幾個問題我還有點困惑 :